/*
 * Copyright (C) 2008 by Xume.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.xume.critann.hibernate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hibernate.CacheMode;
import org.hibernate.Criteria;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.engine.TypedValue;
import org.xume.critann.CriteriaBuilder;
import org.xume.critann.MatchMode;

/**
 * $Id: HibernateCriteriaBuilder.java 24 2008-10-13 21:35:18Z johan.siebens $
 * 
 * @author Johan Siebens
 */
public class HibernateCriteriaBuilder implements CriteriaBuilder<Criterion, Projection> {

	private static final Map<MatchMode, org.hibernate.criterion.MatchMode> matchModeMapping = new HashMap<MatchMode, org.hibernate.criterion.MatchMode>();

	static {
		matchModeMapping.put(MatchMode.ANYWHERE, org.hibernate.criterion.MatchMode.ANYWHERE);
		matchModeMapping.put(MatchMode.START, org.hibernate.criterion.MatchMode.START);
		matchModeMapping.put(MatchMode.END, org.hibernate.criterion.MatchMode.END);
		matchModeMapping.put(MatchMode.EXACT, org.hibernate.criterion.MatchMode.EXACT);
	}

	private Criteria criteria;

	public HibernateCriteriaBuilder(Criteria criteria) {
		this.criteria = criteria;
	}

	public HibernateCriteriaBuilder addCriterion(Criterion criterion) {
		this.criteria.add(criterion);
		return this;
	}

	public Projection avg(String propertyName) {
		return Projections.avg(propertyName);
	}

	public Criterion conjunction(List<Criterion> criterions) {
		return new MultipleCriterionExpression(criterions, "and");
	}

	public Projection count(String propertyName) {
		return Projections.count(propertyName);
	}

	public Criterion disjunction(List<Criterion> criterions) {
		return new MultipleCriterionExpression(criterions, "or");
	}

	public Criterion eq(String propertyName, Object propertyValue) {
		return Restrictions.eq(propertyName, propertyValue);
	}

	public Criterion ge(String propertyName, Object propertyValue) {
		return Restrictions.ge(propertyName, propertyValue);
	}

	public Criterion gt(String propertyName, Object propertyValue) {
		return Restrictions.gt(propertyName, propertyValue);
	}

	public Criterion ilike(String propertyName, String propertyValue, MatchMode matchMode) {
		return Restrictions.ilike(propertyName, propertyValue, matchModeMapping.get(matchMode));
	}

	public Criterion in(String propertyName, Collection<?> values) {
		return Restrictions.in(propertyName, values);
	}

	public Criterion in(String propertyName, Object[] values) {
		return Restrictions.in(propertyName, values);
	}

	public Criterion le(String propertyName, Object propertyValue) {
		return Restrictions.le(propertyName, propertyValue);
	}

	public Criterion like(String propertyName, String propertyValue, MatchMode matchMode) {
		return Restrictions.like(propertyName, propertyValue, matchModeMapping.get(matchMode));
	}

	public List<?> list() {
		return this.criteria.list();
	}

	public Criterion lt(String propertyName, Object propertyValue) {
		return Restrictions.lt(propertyName, propertyValue);
	}

	public Projection max(String propertyName) {
		return Projections.max(propertyName);
	}

	public Projection min(String propertyName) {
		return Projections.min(propertyName);
	}

	public Criterion ne(String propertyName, Object propertyValue) {
		return Restrictions.ne(propertyName, propertyValue);
	}

	public Projection property(String propertyName) {
		return Projections.property(propertyName);
	}

	public Projection rowCount() {
		return Projections.rowCount();
	}

	public HibernateCriteriaBuilder setCacheable(boolean cacheable) {
		this.criteria.setCacheable(cacheable);
		return this;
	}

	public HibernateCriteriaBuilder setCacheMode(CacheMode cacheMode) {
		this.criteria.setCacheMode(cacheMode);
		return this;
	}

	public HibernateCriteriaBuilder setCacheRegion(String cacheRegion) {
		this.criteria.setCacheRegion(cacheRegion);
		return this;
	}

	public HibernateCriteriaBuilder setFirstResult(int firstResult) {
		this.criteria.setFirstResult(firstResult);
		return this;
	}

	public HibernateCriteriaBuilder setFlushMode(FlushMode flushMode) {
		criteria.setFlushMode(flushMode);
		return this;
	}

	public HibernateCriteriaBuilder setMaxResults(int maxResults) {
		this.criteria.setMaxResults(maxResults);
		return this;
	}

	public HibernateCriteriaBuilder setOrderBy(String property) {
		this.criteria.addOrder(Order.asc(property));
		return this;
	}

	public HibernateCriteriaBuilder setProjection(Projection projection) {
		this.criteria.setProjection(projection);
		return this;
	}

	public Projection sum(String propertyName) {
		return Projections.sum(propertyName);
	}

	public Object uniqueResult() {
		return this.criteria.uniqueResult();
	}

	private static class MultipleCriterionExpression implements Criterion {

		private static final long serialVersionUID = 3480072814966926448L;

		private final List<Criterion> criterions;

		private final String op;

		public MultipleCriterionExpression(List<Criterion> criterions, String op) {
			this.criterions = criterions;
			this.op = op;
		}

		public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
			List<TypedValue> result = new ArrayList<TypedValue>();
			for (Criterion criterion : criterions) {
				result.addAll(Arrays.asList(criterion.getTypedValues(criteria, criteriaQuery)));
			}
			return result.toArray(new TypedValue[result.size()]);
		}

		public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
			StringBuilder builder = new StringBuilder();
			builder.append('(');
			for (Iterator<Criterion> iterator = criterions.iterator(); iterator.hasNext();) {
				Criterion criterion = (Criterion) iterator.next();
				builder.append(criterion.toSqlString(criteria, criteriaQuery));
				if (iterator.hasNext()) {
					builder.append(' ').append(op).append(' ');
				}
			}
			builder.append(')');
			return builder.toString();
		}

	}
}
